<?php
/**
 * Classe coração do Moura Control
 * 
 * Esta classe realiza todo o trabalho, através dela todos as chaves são executadas.
 * Ela sempre retorna uma página, que pode ser um template html ou a resposta de um método de algum controller 
 * seja a resposta string ou xml.
 * 
 * PHP version 5
 * 
 * @filesource
 * @package mc
 * @author Adriano Carneiro de Moura adrianodemoura - gmail.com
 * 
 */
/**
 * @package mc
 */
class Mc {
	/**	
 	 * título da página
	 */
	public $pagina_titulo 		= "";
	
	/**
	 * subtitulo da página
	 */
	public $pagina_subtitulo	= "";
	
	/**
	 * codificação de caracter
	 */
	public $charset 			= "utf-8";
	
	/**
	 * nome do template
	 */
	public $template 			= "padrao";
	
	/**
	 * caracter que separa a chave
	 */
	public $separador_chave 	= "-";
	
	/**
	 * Matriz com todos os controllers usados no fluxo
	 */
	public $Controller 			= array();
	
	/**
	 * Matriz com o nome de todos os métodos usados pelas chaves
	 */
	public $metodos				= array();
	
	/**
	 * Matriz com todos os atributos usados pelas chaves
	 */
	public $atributos 			= array();
	
	/**
	 * matriz com todos o arquivos de java script usados no fluxo
	 */
	public $jscripts 			= array();
	
	/**
	 * matriz com todos as folhas de estilo usadas na página
	 */
	public $csss 				= array();
	
	/**
	 * meta extra
	 */
	public $meta_extra 			= array();
	
	/**
	 * método start do calsse principal mc
	 */
	function __construct($template="padrao")
	{
		// iniciando o tempo de carregamento
		$this->inicio = $this->getMicrotime();

		// definindo o template
		$this->template	= $template;
		
		// prevenindo do sql injection
		foreach($_REQUEST as $nome => $valor) $_REQUEST[$nome] = $this->getAntiInjection($valor);
		
		// configurando a url solicitada via GET (usando link permanente)
		$this->setPag();
		
	}
	
	function __destruct()
	{
		if (isset($_SESSION["desativa_cabecalho"])) unset($_SESSION["desativa_cabecalho"]);
		if (isset($_SESSION["desativa_rodape"])) 	unset($_SESSION["desativa_rodape"]);
	}
	
	/**
	 * Configura a url solicita via GET em várias páginas
	 * @return void
	 */
	private function setPag()
	{
		
		$_REQUEST["raiz"] = "http://".$_SERVER["SERVER_NAME"]."/";

		// descobrindo o diretório raiz
		$dir_raiz = str_replace($_SERVER["DOCUMENT_ROOT"],"",$_SERVER["SCRIPT_FILENAME"]);
		$dir_raiz = "/".str_replace("index.php","",$dir_raiz);
		
		// redefinindo raiz
		if ($dir_raiz != "//") $_REQUEST["raiz"] = "http://".$_SERVER["SERVER_NAME"].$dir_raiz;
		
		// removendo //
		$dir_raiz = str_replace("//","/",$dir_raiz);
		$_REQUEST["raiz"] = str_replace("/$dir_raiz","$dir_raiz",$_REQUEST["raiz"]);

		// definindo as páginas
		$uri = str_replace($dir_raiz,"",trim($_SERVER["REQUEST_URI"]));

		$arr_pag = explode("/",$uri);
		$l = 1;
		foreach ($arr_pag as $pag)
		{
			if (strlen($pag) && $pag != $dir_raiz)
			{
				$_REQUEST["pag$l"] = $pag;
				$l++;
			}
		}

	}
	
	/**
	 * Returns microtime for execution time checking
	 * @return float Microtime
	 */
	private function getMicrotime() 
	{
		list($usec, $sec) = explode(' ', microtime());
		return ((float)$usec + (float)$sec);
	}

	/**
	 * Retorna a pagina corrente trocando as chaves por seus respectivos módulo-controller-método ou atributo
	 * @return string $retorno Texto já com suas chaves atualizadas
	 */
	public function getPagina()
	{
		$retorno 	= "";
		$pagina 	= array();
		$cabecalho	= array();
		$corpo		= array();
		$rodape		= array();
		
		// verificando se o template existe
		$arquivo = "templates/".$this->template."/";
		if (!is_dir($arquivo))
		{
			exit("Template <strong>".$this->template."</strong> inexistente !!!");
		}

		// incluindo o css e o js do template
		$arquivo = "templates/".$this->template."/".$this->template.".css";
		if (file_exists($arquivo)) array_push($this->csss,$arquivo);
		$arquivo = "templates/".$this->template."/".$this->template.".js";
		if (file_exists($arquivo)) array_push($this->jscripts,$arquivo);
	
		// se foi requisitada uma chave, já executa aqui mesmo.
		if ($_POST["cha"])
		{
			// resposta da chave
			$corpo = $this->getChave($_POST["cha"]);

			// se for uma requisição ajax, retorna imediatamente, 
			// se não, formata o resultado como array pra continuar a atualização da página.
			if (isset($_POST["ajax"]))
			{
				return $corpo;
			} else
			{
				$corpo = array($corpo);
			}
		}

		// definindo o corpo como sendo a página de instalação
		if ($_REQUEST["pag1"]=="instalacao")
		{
			$cabecalho = array();
			$corpo = array($this->getChave($_SESSION["cha"]));
		}
		
		// definindo o corpo com o conteúdo da página html dentro do template.
		if (!count($corpo))
		{
			$arquivo = "templates/".$this->template."/".$_REQUEST["pag1"].".html";
			if (file_exists($arquivo)) $corpo = file($arquivo);
		}

		// Jabá com módulo site.
		if (!count($corpo))
		{
			if (file_exists("modulos/site/controllerSite.php"))
			{
				$corpo = array($this->getChave("site-site-getPagina"));
			}
		}
		
		// se realmente não achou nada, manda uma mensagem de erro
		if (count($corpo) < 1)
		{
			$corpo = array("<div id='meio'>A página <strong>".$_REQUEST["pag1"]."</strong> não pode ser localizada</div>");
		}

		// atualizando as chaves do corpo		
		$retorno = $this->getAtualizaChaves($corpo);
		
		// atualizando as chaves do cabeçalho
		if (!isset($_SESSION["desativa_cabecalho"]))
		{
			$arquivo = "templates/".$this->template."/cabecalho.html";
			if (file_exists($arquivo)) $cabecalho = file($arquivo);
			$retorno = $this->getAtualizaChaves($cabecalho).$retorno;
		}
		
		// atualizando as chaves do rodapé
		if (!isset($_SESSION["desativa_rodape"]))
		{
			$arquivo = "templates/".$this->template."/rodape.html";
			if (file_exists($arquivo)) $rodape = file($arquivo);
			$retorno .= $this->getAtualizaChaves($rodape);
		}
		// retornando o cabeçalho da pátina xhtml, juntamente com corpo e o rodapé
		//return $this->getCabecalhoHtml().$retorno.$this->getRodapeHtml().$this->getInfo();
		return $this->getCabecalhoHtml().$retorno.$this->getRodapeHtml();
	}
	
	/**
	 * 
	 * @return string $retorno
	 */
	private function getAtualizaChaves($pagina=array())
	{
		// retorno padrão
		$retorno = "";
		
		// Daqui para baixo só vamos trocar as chaves por seus respectivos resultados.
		// Primeiramente vamos jogar todas as chaves numa matriz, dando um loop em cada linha para 
		// descobrir quais chaves existentes na página corrente.
		$arr_chaves	= array();
		foreach($pagina as $linha_n => $linha)
		{
			$chave = "";
			$pegar = 0;
			$retorno .= trim($linha)."\n";
			for($i=0; $i<strlen($linha); $i++)
			{
				if ($pegar && substr($linha,$i,1) != "{" && substr($linha,$i,1) != "}" ) $chave .= substr($linha,$i,1);
				if (substr($linha,$i,2)=="<{") $pegar = 1;
				if (substr($linha,$i,2)=="}>") 
				{
					$pegar = 0;
					array_push($arr_chaves,$chave);
					$chave = "";
				}
			}
		}

		// Dando um loop na matriz chave para executar todos os métodos ou atributos solicitados
		$_chaves = array();
		foreach($arr_chaves as $key)
		{
			// zerando o conteúdo
			$conteudo = "";
			
			// se realmente é uma chave, procura o método ou atributo em outro módulos
			if (strpos($key,$this->separador_chave))
			{
				// quebrando a chave atual em módulo, controller e método ou atributo
				$chave = explode($this->separador_chave,$key);
				
				// recuperando o retorno do método ou atributo da chave corrente
				$conteudo = $this->getChave($key);
			}

			// se não achou nada, tenta achar a chave aqui mesmo no objeto mc
			if (!$conteudo)
			{
				$met_atri = $chave[2]; // método_ou_atributo
				if (method_exists($this,$met_atri))
				{
					$conteudo = $this->$met_atri();
				} elseif (isset($this->$met_atri))
				{
					$conteudo = $this->$met_atri;
				} else
				{
					$conteudo = "impossível localizar: ".$chave[0]."-".$chave[1]."-".$chave[2];
				}
			} else // mas se achou, leva a informação da chave só para faciliar o debug
			{
				$conteudo = $conteudo;
			}

			// incrementando a array nova chaves			
			$_chaves[$key] = $conteudo;
		}
		
		// atualizando o conteúdo da página template, alterando cada chave com o seu respectivo conteúdo.
		foreach($_chaves as $key => $conteudo) $retorno = str_replace("<{".$key."}>",$conteudo,$retorno);

		// retorno		
		return $retorno;

	}

	/**
	 * Retorna o método ou atributo de um controller e de um módulo, se o controller possuir algum atribuito com o mesmo nome aqui do sistema, o mesmo aqui do sistema será alterado.
	 * @param $modulo Matriz informando o Módulo, Controller e Método ou Atributo a ser retornado
	 * @return string $retorno
	 */
	private function getChave($chave="")
	{
		// transformado a chave em matriz
		$chave = explode($this->separador_chave,$chave);
		
		// MVC padrão
		$controller = "Controller";
		$model		= "Model";
		$view 		= "View";
		
		// diretório do módulo ou módulo instalação
		$dir		= strtolower($chave[0]);
		if ($_REQUEST["pag1"]=="instalacao") $dir .= "/instala";
		
		// se existe o controle específico do módulo, troca o MVC padrão
		$arquivo = "modulos/$dir/controller".ucfirst(strtolower($chave[1])).".php";
		if (file_exists($arquivo))
		{
			require_once($arquivo);
			$controller = "Controller".ucfirst(strtolower($chave[1]));
		}
		
		// se existe o modelo específico do módulo, troca o MVC padrão
		$arquivo = "modulos/$dir/model".ucfirst(strtolower($chave[1])).".php";
		if (file_exists($arquivo))
		{
			require_once($arquivo);
			$model = "Model".ucfirst(strtolower($chave[1]));
		}
		
		// se existe o view específico do módulo, troca o MVC padrão
		$arquivo = "modulos/$dir/view".ucfirst(strtolower($chave[1])).".php";
		if (file_exists($arquivo))
		{
			require_once($arquivo);
			$view = "View".ucfirst(strtolower($chave[1]));
		}
		
		// se o controller corrente ainda não foi instanciado		
		if (!is_object($this->Controller[$controller]))
		{
			$this->Controller[$controller] 			= new $controller(); 	// objeto controller
			$this->Controller[$controller]->model	= new $model();
			$this->Controller[$controller]->view	= new $view();
		}
		
		// configurações iniciais
//		$this->Controller[$controller]->chave 			= $chave[0]."-".$chave[1]."-".$chave[2];
		$this->Controller[$controller]->model->chave 	= $chave[0]."-".$chave[1]."-".$chave[2];
		$this->Controller[$controller]->view->template 	= $this->template;
		
		// incrementando css do módulo
		$arquivo = strtolower("modulos/".$chave[0]."/".$chave[0].".css");
		if (file_exists($arquivo)) if (!in_array($arquivo,$this->csss)) array_push($this->csss,$arquivo);
		
		// incrementando js do módulo
		$arquivo = strtolower("modulos/".$chave[0]."/".$chave[0].".js");
		if (file_exists($arquivo)) if (!in_array($arquivo,$this->jscripts)) array_push($this->jscripts,$arquivo);

		// verifica se o atributo ou o método, existe aqui neste controller.
		$met_atri = $chave[2]; // método_ou_atributo
		if (method_exists($this->Controller[$controller],$met_atri))
		{ 
			array_push($this->metodos,$met_atri);
			$retorno = $this->Controller[$controller]->$met_atri();
			
			// verifica se ocorreu algum erro de banco no módulo, em caso afirmativo retorna o erro.
			if (count($this->Controller[$controller]->model->erros) && !$_POST["pag"]=="instalacao")
			{
				$retorno = "";
				foreach($this->Controller[$controller]->model->erros as $erro)
				{
					$_chave = trim(substr($erro,0,strpos($erro,":")));
					if ($_chave == $chave[0]."-".$chave[1]."-".$chave[2])
					{
						$_erro = strtolower(trim(substr($erro,strpos($erro,":")+1,strlen($erro))));
						$retorno .= $_erro."<br />\n";
						
						// verifica o erro, se for coisa grave como falta de banco de dados ou a falta de tabela redireciona para a página de instalação do módulo
						if ( (strpos($_erro,"anco") && strpos($_erro,"desconhecido")) ||
							 (strpos($_erro,"abela") && strpos($_erro,"existe")) ||
							 (strpos($_erro,"able") && strpos($_erro,"doesn"))
						   )
						{
							$redirecionamento 	= 1;
							$_SESSION["cha"] 	= $chave[0]."-instala-getInstalacao";
						}
					}
				}
			}
		} else // se a chave não existe como método, tenta achar como atributo
		{
			if (isset($this->Controller[$controller]->$met_atri))
			{
				$retorno = $this->Controller[$controller]->$met_atri;
				if (isset($this->$met_atri))
				{
					array_push($this->atributos,$met_atri);
					$this->$met_atri = $retorno;
				}
			}
		}
		
		// se a chave pediu algum cabeçalho especial
		if ($this->Controller[$controller]->meta)
		{
			if (!in_array($this->Controller[$controller]->meta,$this->meta_extra))
			{
				array_push($this->meta_extra,$this->Controller[$controller]->meta);
			}
		}
	
		// redirecionando para instalação do módulo
		if ($redirecionamento && $_REQUEST["pag1"]!="instalacao")
		{
			exit("<script>document.location.href='instalacao';</script>");
		}

		// retorno
		return $retorno;
	}

	/**
	 * Função antiInjection
	 * Retorna o conteúdo de $_REQUEST, livre de comandos maliciosos
	 * @param string $cod texto a ser filtrado
	 * @return string $cod 
	 */
	public function getAntiInjection($cod="")	{
		// remove palavras que contenham sintaxe sql
		$cod = preg_replace("/(from|select|insert|delete|where|drop table|>|\"|show tables|#|\*|\\\\)/i","",$cod);

		//limpa espaço vazio
		$cod = trim($cod);

		//tira tags html e php
		$cod = strip_tags($cod);

		//Adiciona barras invertidas a uma string
		$cod = addslashes($cod);

		return $cod;
	}

	
	/**
	 * Retorna o cabeçalho da página
	 * @return $retorno string
	 */
	public function getCabecalhoHtml()
	{
		$retorno = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">\n";
		$retorno .= "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n";
		$retorno .= "<head>\n\n";
		$retorno .= "\t<!-- meta -->\n";
		$retorno .= "\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n\n";
		
		$retorno .= "\t<!-- título da página template -->\n";
		$retorno .= "\t<title>".$this->pagina_titulo."</title>\n\n";
		
		$retorno .= "\t<!-- ícone template -->\n";
		$retorno .= "\t<link type=\"image/x-icon\" 	href=\"".$_REQUEST["raiz"]."templates/".$this->template."/favicon.ico\" 	rel=\"shortcut icon\" />\n\n";
		
		// cabeçalho extra, provavelmente incrementados por algum controller específico.
		$retorno .= "\t<!-- cabeçalho extra -->\n";
		foreach($this->meta_extra as $meta)
		{
			$retorno .= "\t$meta\n";
		}
		
		$retorno .= "\t<!-- css -->\n";
		foreach($this->csss as $css)
		{
			$retorno .= "\t<link type=\"text/css\" href=\"".$_REQUEST["raiz"]."$css\"\t\trel=\"stylesheet\" media=\"all\" />\n";
		}
		$retorno .= "\n";

		$retorno .= "\t<!-- javascript -->\n";
		foreach($this->jscripts as $jscript)
		{
			$retorno .= "\t<script type=\"text/javascript\" src=\"".$_REQUEST["raiz"].$jscript."\"></script>\n";
		}
		$retorno .= "\n";

		$retorno .= "</head>\n";
		$retorno .= "<body>\n";
		
		return $retorno;
	}
	
	/**
	 * Retorna o cabeçalho da página
	 * @return $retorno string
	 */
	public function getRodapeHtml()
	{
		$retorno = "\r";
		$retorno .= "</body>\n";
		$retorno .= "</html>\n";
		$retorno .= "<!-- tempo de carregamento: " . round($this->getMicrotime() - $this->inicio, 4) . " segundos -->\n"; 
		return $retorno;
	}
	
	/**
	 * Retorna informações sobre o sistema
	 * @return $retorno string
	 */
	public function getInfo()
	{
		$retorno = "\r";
		$retorno .= "<div id='debug'>";
		// informando os controladores usados
		$retorno .= "<strong>Página: ".$_REQUEST["pag1"]."</strong><br />\n";
		$retorno .= "Esta página instanciou ".count($this->Controller)." Controller(s).<br />";
		$retorno .= "<br />\n";
		$retorno .= "<strong>Chaves do tipo método:</strong><br />\n";
		foreach($this->metodos as $metodo)
		{
			$retorno .= "\t\t$metodo<br />\n";
		}
		$retorno .= "<strong>Chaves do tipo atributo:</strong><br />\n";
		foreach($this->atributos as $atributo)
		{
			$retorno .= "\t\t$atributo<br />\n";
		}
		$retorno .= nl2br(print_r($this->chaves,true));
		$retorno .= "<br />\n";
		$retorno .= "<strong>Sessão:</strong><br />\n";
		$retorno .= nl2br(print_r($_SESSION,true));
		$retorno .= "</div>\n";
		return $retorno;
	}
	
	/**
	 * Retorna o formulário de instalação do módulo
	 * @param string $chave
	 * @return $retorno string
	 */
	public function getPaginaInstalaModulo()
	{

		// recuperando a página de instalação do módulo		
		$retorno = $this->getChave($_SESSION["cha"]);
		
		// limpando a sessão
		unset($_SESSION["cha"]);
		unset($_SESSION["erro"]);

		// retorno
		return $retorno;
	}

}

?>
